// Copyright 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "power_manager/powerd/system/input_watcher.h"

#include <fcntl.h>
#include <linux/input.h>
#include <linux/vt.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <base/callback.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/message_loop/message_loop.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>

#include "power_manager/common/power_constants.h"
#include "power_manager/common/prefs.h"
#include "power_manager/common/util.h"
#include "power_manager/powerd/system/acpi_wakeup_helper.h"
#include "power_manager/powerd/system/event_device_interface.h"
#include "power_manager/powerd/system/input_observer.h"
#include "power_manager/powerd/system/udev.h"

namespace power_manager {
namespace system {

namespace {

const char kSysClassInputPath[] = "/sys/class/input";
const char kDevInputPath[] = "/dev/input";
const char kInputBaseName[] = "event";

const char kInputMatchPattern[] = "input*";
const char kUsbMatchString[] = "usb";
const char kBluetoothMatchString[] = "bluetooth";

// Path to the console device where VT_GETSTATE ioctls are made to get the
// currently-active VT.
const char kConsolePath[] = "/dev/tty0";

// Given a string |name| consisting of kInputBaseName followed by a base-10
// integer, extracts the integer to |num_out|. Returns false if |name| didn't
// match the expected format.
bool GetInputNumber(const std::string& name, int* num_out) {
  if (!StartsWithASCII(name, kInputBaseName, true))
    return false;
  size_t base_len = strlen(kInputBaseName);
  return base::StringToInt(name.substr(base_len, name.size() - base_len),
                           num_out);
}

// If |event| came from a lid switch, copies its state to |state_out| and
// returns true. Otherwise, leaves |state_out| untouched and returns false.
bool GetLidStateFromEvent(const input_event& event, LidState* state_out) {
  if (event.type != EV_SW || event.code != SW_LID)
    return false;

  *state_out = event.value == 1 ? LID_CLOSED : LID_OPEN;
  return true;
}

// If |event| came from a power button, copies its state to |state_out| and
// returns true. Otherwise, leaves |state_out| untouched and returns false.
bool GetPowerButtonStateFromEvent(const input_event& event,
                                  ButtonState* state_out) {
  if (event.type != EV_KEY || event.code != KEY_POWER)
    return false;

  switch (event.value) {
    case 0: *state_out = BUTTON_UP;      break;
    case 1: *state_out = BUTTON_DOWN;    break;
    case 2: *state_out = BUTTON_REPEAT;  break;
    default: LOG(ERROR) << "Unhandled button state " << event.value;
  }
  return true;
}

}  // namespace

const char InputWatcher::kInputUdevSubsystem[] = "input";
const char InputWatcher::kPowerButtonToSkip[] = "LNXPWRBN";
const char InputWatcher::kPowerButtonToSkipForLegacy[] = "isa";

InputWatcher::InputWatcher()
    : dev_input_path_(kDevInputPath),
      sys_class_input_path_(kSysClassInputPath),
      lid_device_(NULL),
      hover_device_(NULL),
      use_lid_(true),
      lid_state_(LID_OPEN),
      detect_hover_(false),
      hovering_(false),
      current_multitouch_slot_(0),
      multitouch_slots_hover_state_(0),
      power_button_to_skip_(kPowerButtonToSkip),
      console_fd_(-1),
      udev_(NULL),
      weak_ptr_factory_(this) {
}

InputWatcher::~InputWatcher() {
  if (udev_)
    udev_->RemoveSubsystemObserver(kInputUdevSubsystem, this);
  if (console_fd_ >= 0)
    close(console_fd_);
}

bool InputWatcher::Init(
    scoped_ptr<EventDeviceFactoryInterface> event_device_factory,
    PrefsInterface* prefs,
    UdevInterface* udev) {
  event_device_factory_ = event_device_factory.Pass();
  udev_ = udev;

  prefs->GetBool(kUseLidPref, &use_lid_);
  if (!use_lid_)
    lid_state_ = LID_NOT_PRESENT;

  bool legacy_power_button = false;
  if (prefs->GetBool(kLegacyPowerButtonPref, &legacy_power_button) &&
      legacy_power_button)
    power_button_to_skip_ = kPowerButtonToSkipForLegacy;

  prefs->GetBool(kDetectHoverPref, &detect_hover_);

  udev_->AddSubsystemObserver(kInputUdevSubsystem, this);

  if (!base::DirectoryExists(dev_input_path_) ||
      access(dev_input_path_.value().c_str(), R_OK|X_OK) != 0) {
    LOG(ERROR) << dev_input_path_.value() << " isn't a readable directory";
    return false;
  }
  base::FileEnumerator enumerator(
      dev_input_path_, false, base::FileEnumerator::FILES);
  for (base::FilePath path = enumerator.Next(); !path.empty();
       path = enumerator.Next()) {
    const std::string name = path.BaseName().value();
    int num = -1;
    if (GetInputNumber(name, &num))
      HandleAddedInput(name, num);
  }
  return true;
}

void InputWatcher::AddObserver(InputObserver* observer) {
  DCHECK(observer);
  observers_.AddObserver(observer);
}

void InputWatcher::RemoveObserver(InputObserver* observer) {
  DCHECK(observer);
  observers_.RemoveObserver(observer);
}

LidState InputWatcher::QueryLidState() {
  if (!lid_device_)
    return LID_NOT_PRESENT;

  const uint32_t device_types = GetDeviceTypes(lid_device_);
  while (true) {
    // Stop when we fail to read any more events.
    std::vector<input_event> events;
    if (!lid_device_->ReadEvents(&events))
      break;

    // Get the state from the last lid event (|events| may also contain non-lid
    // events).
    for (std::vector<input_event>::const_reverse_iterator it =
             events.rbegin(); it != events.rend(); ++it) {
      if (GetLidStateFromEvent(*it, &lid_state_))
        break;
    }

    queued_events_.reserve(queued_events_.size() + events.size());
    for (auto event : events)
      queued_events_.push_back(std::make_pair(event, device_types));
    VLOG(1) << "Queued " << events.size()
            << " event(s) while querying lid state";
  }

  if (!queued_events_.empty()) {
    send_queued_events_task_.Reset(
        base::Bind(&InputWatcher::SendQueuedEvents, base::Unretained(this)));
    base::MessageLoop::current()->PostTask(
        FROM_HERE, send_queued_events_task_.callback());
  }

  return lid_state_;
}

bool InputWatcher::IsUSBInputDeviceConnected() const {
  base::FileEnumerator enumerator(sys_class_input_path_, false,
      static_cast<::base::FileEnumerator::FileType>(
          base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS),
      kInputMatchPattern);
  for (base::FilePath path = enumerator.Next(); !path.empty();
       path = enumerator.Next()) {
    base::FilePath symlink_path;
    if (!base::ReadSymbolicLink(path, &symlink_path))
      continue;
    const std::string& path_string = symlink_path.value();
    // Skip bluetooth devices, which may be identified as USB devices.
    if (path_string.find(kBluetoothMatchString) != std::string::npos)
      continue;
    // Now look for the USB devices that are not bluetooth.
    size_t position = path_string.find(kUsbMatchString);
    if (position == std::string::npos)
      continue;
    // Now that the string "usb" has been found, make sure it is a whole word
    // and not just part of another word like "busbreaker".
    bool usb_at_word_head =
        position == 0 || !IsAsciiAlpha(path_string.at(position - 1));
    bool usb_at_word_tail =
        position + strlen(kUsbMatchString) == path_string.size() ||
        !IsAsciiAlpha(path_string.at(position + strlen(kUsbMatchString)));
    if (usb_at_word_head && usb_at_word_tail)
      return true;
  }
  return false;
}

int InputWatcher::GetActiveVT() {
  // It's not worthwhile creating an interface around this single ioctl to query
  // the active VT. Defer opening the console until this method is called so
  // that unit tests can step around this code.
  if (console_fd_ < 0) {
    if ((console_fd_ = open(kConsolePath, O_WRONLY)) == -1) {
      PLOG(ERROR) << "Unable to open " << kConsolePath;
      return -1;
    }
  }
  struct vt_stat state;
  if (ioctl(console_fd_, VT_GETSTATE, &state) == -1) {
    PLOG(ERROR) << "VT_GETSTATE ioctl on " << kConsolePath << "failed";
    return -1;
  }
  return state.v_active;
}

void InputWatcher::OnUdevEvent(const std::string& subsystem,
                               const std::string& sysname,
                               UdevAction action) {
  DCHECK_EQ(subsystem, kInputUdevSubsystem);
  int input_num = -1;
  if (GetInputNumber(sysname, &input_num)) {
    if (action == UDEV_ACTION_ADD)
      HandleAddedInput(sysname, input_num);
    else if (action == UDEV_ACTION_REMOVE)
      HandleRemovedInput(input_num);
  }
}

uint32_t InputWatcher::GetDeviceTypes(
    const EventDeviceInterface* device) const {
  uint32_t device_types = DEVICE_NONE;
  if (power_button_devices_.count(device))
    device_types |= DEVICE_POWER_BUTTON;
  if (device == lid_device_)
    device_types |= DEVICE_LID_SWITCH;
  if (device == hover_device_)
    device_types |= DEVICE_HOVER;
  return device_types;
}

void InputWatcher::OnNewEvents(EventDeviceInterface* device) {
  SendQueuedEvents();

  std::vector<input_event> events;
  if (!device->ReadEvents(&events))
    return;

  VLOG(1) << "Read " << events.size() << " event(s) from "
          << device->GetDebugName();
  const uint32_t device_types = GetDeviceTypes(device);
  for (size_t i = 0; i < events.size(); ++i) {
    // Update |lid_state_| here instead of in ProcessEvent() so we can avoid
    // modifying it in response to queued events.
    if (device_types & DEVICE_LID_SWITCH)
      GetLidStateFromEvent(events[i], &lid_state_);
    ProcessEvent(events[i], device_types);
  }
}

void InputWatcher::ProcessEvent(const input_event& event,
                                uint32_t device_types) {
  LidState lid_state = LID_OPEN;
  if ((device_types & DEVICE_LID_SWITCH) &&
      GetLidStateFromEvent(event, &lid_state)) {
    VLOG(1) << "Notifying observers about lid " << LidStateToString(lid_state)
            << " event";
    FOR_EACH_OBSERVER(InputObserver, observers_, OnLidEvent(lid_state));
  }

  ButtonState button_state = BUTTON_DOWN;
  if ((device_types & DEVICE_POWER_BUTTON) &&
      GetPowerButtonStateFromEvent(event, &button_state)) {
    VLOG(1) << "Notifying observers about power button "
            << ButtonStateToString(button_state) << " event";
    FOR_EACH_OBSERVER(InputObserver, observers_,
                      OnPowerButtonEvent(button_state));
  }

  if (device_types & DEVICE_HOVER)
    ProcessHoverEvent(event);
}

void InputWatcher::ProcessHoverEvent(const input_event& event) {
  if (event.type == EV_ABS && event.code == ABS_MT_SLOT) {
    VLOG(2) << "ABS_MT_SLOT " << event.value;
    // ABS_MT_SLOT events announce the slot that following multitouch events
    // will refer to.
    if (event.value < 0 ||
        event.value >=
        static_cast<int>(sizeof(multitouch_slots_hover_state_) * 8)) {
      LOG(WARNING) << "Ignoring ABS_MT_SLOT event for slot "
                   << event.value;
      current_multitouch_slot_ = -1;
    } else {
      current_multitouch_slot_ = event.value;
    }
  } else if (event.type == EV_ABS && event.code == ABS_MT_TRACKING_ID) {
    VLOG(2) << "ABS_MT_TRACKING_ID " << event.value;
    // ABS_MT_TRACKING_ID events associate a tracking ID with the current slot,
    // with -1 indicating that the slot is unused. Use them as a proxy for
    // whether the slot is reporting a hover (or touch).
    if (current_multitouch_slot_ >= 0) {
      const uint64_t slot_bit =
          static_cast<uint64_t>(1) << current_multitouch_slot_;
      if (event.value >= 0)
        multitouch_slots_hover_state_ |= slot_bit;
      else
        multitouch_slots_hover_state_ &= ~slot_bit;
    }
  } else if (event.type == EV_SYN && event.code == SYN_REPORT) {
    // SYN_REPORT events indicate the end of the current set of multitouch data.
    // Check whether the overall hovering state is different from before and
    // notify observers if so.
    VLOG(2) << "SYN_REPORT";
    bool hovering = multitouch_slots_hover_state_ != 0;
    if (hovering != hovering_) {
      VLOG(1) << "Notifying observers about hover state change to "
              << (hovering ? "on" : "off");
      hovering_ = hovering;
      FOR_EACH_OBSERVER(InputObserver, observers_,
                        OnHoverStateChanged(hovering_));
    }
  }
}

void InputWatcher::HandleAddedInput(const std::string& input_name,
                                    int input_num) {
  if (event_devices_.count(input_num) > 0) {
    LOG(WARNING) << "Input " << input_num << " already registered";
    return;
  }

  const base::FilePath path = dev_input_path_.Append(input_name);
  linked_ptr<EventDeviceInterface> device(event_device_factory_->Open(path));
  if (!device.get()) {
    LOG(ERROR) << "Failed to open " << path.value();
    return;
  }

  const std::string phys = device->GetPhysPath();
  if (StartsWithASCII(phys, power_button_to_skip_, true /* case_sensitive */)) {
    VLOG(1) << "Skipping event device with phys path: " << phys;
    return;
  }

  bool should_watch = false;
  if (device->IsPowerButton()) {
    LOG(INFO) << "Watching power button: " << device->GetDebugName();
    should_watch = true;
    power_button_devices_.insert(device.get());
  }

  // Note that it's possible for a power button and lid switch to share a single
  // event device.
  if (use_lid_ && device->IsLidSwitch()) {
    if (lid_device_) {
      LOG(WARNING) << "Skipping additional lid switch device "
                   << device->GetDebugName();
    } else {
      LOG(INFO) << "Watching lid switch: " << device->GetDebugName();
      should_watch = true;
      lid_device_ = device.get();
      lid_state_ = device->GetInitialLidState();
      VLOG(1) << "Initial lid state is " << LidStateToString(lid_state_);
    }
  }

  if (detect_hover_ && device->HoverSupported() && device->HasLeftButton()) {
    if (hover_device_) {
      LOG(WARNING) << "Skipping additional hover device "
                   << device->GetDebugName();
    } else {
      LOG(INFO) << "Watching hover device: " << device->GetDebugName();
      should_watch = true;
      hover_device_ = device.get();
    }
  }

  if (should_watch) {
    device->WatchForEvents(base::Bind(&InputWatcher::OnNewEvents,
                                      weak_ptr_factory_.GetWeakPtr(),
                                      base::Unretained(device.get())));
    event_devices_.insert(std::make_pair(input_num, device));
  }
}


void InputWatcher::HandleRemovedInput(int input_num) {
  InputMap::iterator it = event_devices_.find(input_num);
  if (it == event_devices_.end())
    return;
  LOG(INFO) << "Stopping watching " << it->second->GetDebugName();
  power_button_devices_.erase(it->second.get());
  if (lid_device_ == it->second.get())
    lid_device_ = NULL;
  if (hover_device_ == it->second.get())
    hover_device_ = NULL;
  event_devices_.erase(it);
}

void InputWatcher::SendQueuedEvents() {
  for (auto event_pair : queued_events_)
    ProcessEvent(event_pair.first, event_pair.second);
  queued_events_.clear();
}

}  // namespace system
}  // namespace power_manager
